STEP 3 - Review results from DTM_inferLDATopics_LabelCorpus

This notebook: 1. Reads in labeled outputs from 06_pq_model.Rmd (“06_pq_labels.csv”) 2. Joins “06_pq_labels.csv” with “01_pq_metaclean.csv” 3. Subsets dataset based on labels and percent probabilities 4. Writes subsets to CSVs

Notes

  1. “SUBSETTING” keyword for searching 06_pq_label_review.Rmd
#load data
# 01_pq_metaclean.csv
pq_metaclean <- data.table::fread('Data/02_Working/01_pq_metaclean.csv')
# read in columns as characters so that doc id does not read in as numeric
# 04_pq_labels.csv
pq_labels <- data.table::fread(paste0('Data/02_Working/',rFileModelNum,'_pq_labels.csv'), colClasses = 'character')

# Head displays the first 6 rows of the data.table
#head(pq_metaclean)
#head(pq_labels)
# displays column names
print("pq_metaclean columns:")
[1] "pq_metaclean columns:"
names(pq_metaclean)
 [1] "Title"                   "Publication title"       "Publication year"        "Document URL"            "Full text"              
 [6] "Links"                   "Section"                 "Publication subject"     "ISSN"                    "Copyright"              
[11] "Abstract"                "Publication info"        "Last updated"            "Place of publication"    "Location"               
[16] "Author"                  "Publisher"               "Identifier / keyword"    "Source type"             "ProQuest document ID"   
[21] "Country of publication"  "Language of publication" "Publication date"        "Subject"                 "Database"               
[26] "Document type"          
print("")
[1] ""
print("pq_labels columns:")
[1] "pq_labels columns:"
names(pq_labels)
[1] "document" "topic"    "val"     
nrow(pq_metaclean);nrow(pq_labels)
[1] 6239
[1] 4031

join cleaned dataset with labels

# join tables
# inner_join because pq_metaclean was subset based on topic 2 when the model was re-ran in "04_pq_model.Rmd"
# so we only want where the Proquest ID exists in both the original dataset and the labels.
pq_metajoin <- pq_labels %>% 
  inner_join(pq_metaclean, by = c("document" = "ProQuest document ID"))

pq_metajoin <- pq_metajoin %>%
  rename(`ProQuest document ID` = document)

nrow(pq_metajoin);head(pq_metajoin)
[1] 4030
pq_empty<-pq_metajoin[pq_metajoin$topic == "" | is.na(pq_metajoin$topic),]
nrow(pq_empty)
[1] 0
# write labeled corpus to CSV
outputFileName = "pq_topics"
outputFile = paste(outputFolder,rFileNum,"_",outputFileName,".xlsx",sep="")
if (!file.exists(outputFile) | overwrite) {
  write.xlsx(pq_metajoin, outputFile, row.names=FALSE)
  #write.htmltable(pq_metajoin,title=outputFile, outputFile, sortby="topic","val")
}

Code now ready for SUBSETTING if you want to skip the following sections –

exploratory data analysis of topics generated

Do we want to keep both topics, or drop one of the topics and dig deeper into the other topic?

number of topics by publication title

plotTitle = "Count of Articles by Topic and Publication Title"

# count by pub title and topic
count_topic_pubtitle <-pq_metajoin %>%
  group_by(`Publication title`,topic) %>% 
  summarise(n= n()) %>%
  arrange(as.numeric(topic),as.numeric(n))
`summarise()` has grouped output by 'Publication title'. You can override using the `.groups` argument.
count_topic_pubtitle

# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_pubtitle.png"))
png(outputPNGFileName,height=6,width=12, units='in', res=300)
ggplot(count_topic_pubtitle, aes(x = as.factor(as.numeric(topic)), y = n, fill = `Publication title`, label = n )) +
  geom_bar(stat = "identity") +
  geom_text(size = 3, position = position_stack(vjust = 0.5)) +
  ggtitle(plotTitle) +
  theme(plot.title = element_text(hjust = 0.5)) +
  xlab("Topic") +
  ylab("Article Count") +
  labs(fill = "Topic")
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/count_topic_pubtitle.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_pubtitle.png"))

number of topics by location

Count articles by topic and year

Important to consider the total articles/year in addition to the raw count of articles per topic. Peaks in articles may simply be due to an overall increase of articles for a particular year.

# article count per year and topic
count_topic_year <-pq_metajoin %>%
  group_by(`Publication year`,topic) %>% 
  summarise(n= n()) %>%
  arrange(as.numeric(topic),as.numeric(`Publication year`))
`summarise()` has grouped output by 'Publication year'. You can override using the `.groups` argument.
count_topic_year

# article count per year
count_year <-pq_metajoin %>%
  group_by(`Publication year`) %>% 
  summarise(perYear= n()) %>%
  arrange(as.numeric(`Publication year`))

count_topic_year<-count_topic_year %>%
  left_join(count_year, by = "Publication year")

count_topic_year<-count_topic_year %>%
  mutate(ratio = round(n/perYear,2))

plotTitle = "Count of Articles by Topic and Year"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_year.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = n)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("Article Count") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/count_topic_year.png"
dev.off()
null device 
          1 
plotTitle = "Count of Articles by Topic and Year with Year Total"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("count_topic_year_tot.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = n)) +
  geom_line(linetype = "dashed", color="black", aes(y = perYear)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("Article Count") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/count_topic_year_tot.png"
dev.off()
null device 
          1 
plotTitle = "Ratio of Articles by Topic and Year"
# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("ratio_topic_year.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)
ggplot(count_topic_year, aes(x = `Publication year`, colour = as.factor(as.numeric(topic)))) +
  geom_line(aes(y = ratio)) +
  scale_x_continuous(breaks=seq(min(na.omit(count_topic_year$`Publication year`)), max(na.omit(count_topic_year$`Publication year`)),1)) +
  theme(axis.text.x = element_text(angle = 90),
        plot.title = element_text(hjust = 0.5)) +
  facet_wrap(vars(as.numeric(topic))) +
  ggtitle(plotTitle) +
  xlab("Publication Year") +
  ylab("(Article Count)/(Article Total)") +
  guides(colour=FALSE) 
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/ratio_topic_year.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_year.png"))

knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/count_topic_year_tot.png"))

knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/ratio_topic_year.png"))

Coherence score by topic

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/coherence_score_topic.png"))

Wordclouds

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/1_lda_topic_wc.png"))

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/2_lda_topic_wc.png"))

Count of Article by Topic

# bar chart
knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/topic_count_bar.png"))


# pie chart
knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/topic_count_pie.png"))

Count of Probabilities by Percentage

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/prob_count.png"))

Count of Topic Probabilities by Percentage

knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/prob_topic_count.png"))

#5. Visualising of topics in a dendrogram - not enough topics (<2)

# knitr::include_graphics(paste0("Images/",rFileModelNum,"_pq_model/hclust_dendrogram.png"))

START SUBSETTING - Subset Function

topic_subset_csv <- function(outputFolderName, rFileNum, outputFileName, inputCorpus, minPerc, maxPerc, theTopic, overwrite)

  • outputFolderName = folder to save to – i.e., “Data/02_Working/” (this is set at the top of the code where the libraries are loaded/installed)
  • rFileNum = The number in the r file – i.e., “05” (this is set at the top of the code where the libraries are loaded/installed)
  • outputFileName = the file name to save as – i.e., “pq_topic5_90perc” (this needs to be set at least 1 line above where the function is ran)
  • inputCorpus = the labeled corpus – i.e., pq_metajoin (this is created shortly after where libraries are loaded/installed in review.Rmd by joining pq_metaclean with pq_labels)
  • minPerc = minimum percentage – i.e., 0.9 (this needs to be set at least 1 line above where the function is ran)
  • maxPerc = maximum percentage – i.e., 1 (this needs to be set at least 1 line above where the function is ran)
  • theTopic = the topic category – i.e., “1” (this needs to be set at least 1 line above where the function is ran)
  • overwrite = whether to overwrite the file if it already exists – i.e., FALSE (this needs to be set at least 1 line above where the function is ran)

Subset topic 1

# subset corpus to unique identifier & full text of article
# investigate topic 1

outputFileName_1t <- "pq_topic1"
minPerc <- 0
maxPerc <- 1
theTopic <- "1"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic1_subset<-topic_subset_csv(outputFolder, rFileNum, outputFileName_1t, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic1 
head(pq_topic1_subset);nrow(pq_topic1_subset)
[1] 1025

Subset topic 2

# subset corpus to unique identifier & full text of article
# investigate topic 2

outputFileName_1t <- "pq_topic2"
minPerc <- 0
maxPerc <- 1
theTopic <- "2"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic1_subset<-topic_subset_csv(outputFolder, rFileNum, outputFileName_1t, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic2 
head(pq_topic1_subset);nrow(pq_topic1_subset)
[1] 3005

Subset topic 1, 90%

# subset corpus to unique identifier & full text of article
# investigate topic 1, 90%
outputFile_1t90perc <- "pq_topic1_90perc"
minPerc <- 0.9
maxPerc <- 1
theTopic <- "1"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic1_90perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_1t90perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic1_90perc 
head(pq_topic1_90perc);nrow(pq_topic1_90perc)
[1] 733

Subset topic 2, 90%

# subset corpus to unique identifier & full text of article
# investigate topic 2, 90%
outputFile_5t90perc <- "pq_topic2_90perc"
minPerc <- 0.9
maxPerc <- 1
theTopic <- "2"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_topic5_90perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_5t90perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_topic2_90perc 
head(pq_topic5_90perc);nrow(pq_topic5_90perc)
[1] 1994

Subset 50-60%-ers

# subset corpus to unique identifier & full text of article
# investigate all topics, 50-60%
outputFile_56perc <- "pq_56perc"
minPerc <- 0.5
maxPerc <- 0.6
theTopic <- "all"

## e.g, minPerc = 0 & maxPerc = 0.4 is zero to 0.3999999999 ...
pq_56perc<-topic_subset_csv(outputFolder, rFileNum, outputFile_56perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_56perc 
head(pq_56perc);nrow(pq_56perc)
[1] 224

Subset 0-60%-ers

# subset corpus to unique identifier & full text of article
# investigate all topics, 0-60%
# if within the function you write overwrite=TRUE, then the output CSV file will be re-written. 
# overwrite, outputFolder, rFileName are set in the first chunk of code


outputFileName_06perc <- "pq_06perc"
minPerc <- 0
maxPerc <- 0.6
theTopic <- "all"

## e.g, minPerc = 0 & maxPerc = 0.6 is zero to 0.59999999 ...
pq_perc06_subset<-topic_subset_csv(outputFolder, rFileNum, outputFileName_06perc, pq_metajoin , minPerc, maxPerc, theTopic, overwrite)

Already exists, loading: pq_06perc 
head(pq_perc06_subset);nrow(pq_topic1_subset)
[1] 3005
unique(pq_perc06_subset$topic)
[1] "2" "1"
min(pq_perc06_subset$val)
[1] "0.50066"
max(pq_perc06_subset$val)
[1] "0.59996"

END SUBSETTING

explore term frequencies by year - how do words in our corpus change over time?

Referenced walk-through from: https://cran.r-project.org/web/packages/tidytext/vignettes/tidying_casting.html Are articles normalized based on the number of articles per year

# subset corpus to unique identifier & year
pq_time <- pq_metaclean %>% 
  select(`ProQuest document ID`, `Publication year`)

# tokens generated from 06_pq_model.Rmd
outputTokenFile = paste0(rFileModelNum,"_tokens.RData")
tokensFileName = file.path(outputFolder,outputTokenFile)

load(file=tokensFileName)

tokens <- tokens %>% 
  full_join(pq_time, by = c("ProQuest document ID" = "ProQuest document ID")) %>%
  rename(Year = `Publication year`) %>%
  rename(pq_id = `ProQuest document ID`) %>%
  mutate_at(vars(Year), funs(as.integer))
`funs()` was deprecated in dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
rm(pq_time)

outputFreqsFile = paste0(rFileNum,"_tokens_freq")
tokens_freq<-create_ifnot_tokens_freq(outputFolder, outputFreqsFile, tokens, overwrite)

Already exists, loading: 07_tokens_freq 
outputFreqModelFile = paste0(rFileNum,"_freq_models")
freq_models <- create_ifnot_freqmodels(outputFolder, outputFreqModelFile, tokens_freq, overwrite)

Already exists, loading: 07_freq_models 

model results

freq_models %>%
  filter(term == "Year") %>%
  arrange(desc(abs(estimate)))

Models displayed as a volcano plot, which compares the effect size with the significance

# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("word_change_over_time.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)

freq_models %>%
  mutate(adjusted.p.value = p.adjust(p.value)) %>%
  ggplot(aes(estimate, adjusted.p.value)) +
  geom_point() +
  scale_y_log10() +
  geom_text(aes(label = word), vjust = 1, hjust = 1,
            check_overlap = TRUE) +
  xlab("Estimated change over time") +
  ylab("Adjusted p-value")


print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/word_change_over_time.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/word_change_over_time.png"))

Top 6 terms that have changed in frequency over time

# save plot in png format
outputPNGFileName <- file.path(outputPngFolder,paste0("top_word_change_over_time.png"))
png(outputPNGFileName,height=5,width=15, units='in', res=300)

freq_models %>%
  top_n(6, abs(estimate)) %>%
  inner_join(tokens_freq) %>%
  ggplot(aes(Year, percent)) +
  geom_point() +
  geom_smooth() +
  facet_wrap(~ word) +
  scale_y_continuous(labels = percent_format()) +
  ylab("Frequency of word in speech")
Joining, by = "word"
print(paste("Plot saved as:",outputPNGFileName))
[1] "Plot saved as: Images//07_pq_review/top_word_change_over_time.png"
dev.off()
null device 
          1 
knitr::include_graphics(paste0("Images/",rFileNum,"_pq_review/top_word_change_over_time.png"))

LS0tDQp0aXRsZTogInBxX2xhYmVsX3JldmlldyINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQojIFNURVAgMyAtIFJldmlldyByZXN1bHRzIGZyb20gRFRNX2luZmVyTERBVG9waWNzX0xhYmVsQ29ycHVzDQoNClRoaXMgbm90ZWJvb2s6DQoxLiBSZWFkcyBpbiBsYWJlbGVkIG91dHB1dHMgZnJvbSAwNl9wcV9tb2RlbC5SbWQgKCIwNl9wcV9sYWJlbHMuY3N2IikNCjIuIEpvaW5zICIwNl9wcV9sYWJlbHMuY3N2IiB3aXRoICIwMV9wcV9tZXRhY2xlYW4uY3N2Ig0KMy4gU3Vic2V0cyBkYXRhc2V0IGJhc2VkIG9uIGxhYmVscyBhbmQgcGVyY2VudCBwcm9iYWJpbGl0aWVzDQo0LiBXcml0ZXMgc3Vic2V0cyB0byBDU1ZzDQoNCiMgTm90ZXMNCjEuICJTVUJTRVRUSU5HIiBrZXl3b3JkIGZvciBzZWFyY2hpbmcgMDZfcHFfbGFiZWxfcmV2aWV3LlJtZA0KDQpgYGB7ciwgZWNobz1GQUxTRSwgcmVzdWx0cz0iaGlkZSIsIG1lc3NhZ2VzPUZBTFNFfQ0KIyBMb2FkIGFuZCBJbnN0YWxsIExpYnJhcmllcw0Kc291cmNlKCJTRU5fZnVuY3Rpb25zLlIiKQ0KDQojIyBDaGVjayBsaWJyYXJpZXMgJiBpbnN0YWxsDQpMaWJyYXJ5TGlzdDwtYygic3RyaW5nciIsImRhdGEudGFibGUiLCJkcGx5ciIsInRpZHlyIiwibWFncml0dHIiLCJOTFAiLCJ0aWR5dGV4dCIsInRtIiwiZ2dwbG90MiIsDQogICAgICAgICAgICAgICAic2NhbGVzIiwgImdnd29yZGNsb3VkIiwidGV4dG1pbmVSIiwiZGlnZXN0IiwgImJyb29tIiwgInN0cmluZ2kiLCAieGxzeCIpDQppbnN0YWxsX29yX2xvYWRfcGFjayhMaWJyYXJ5TGlzdCkNCg0KDQpvdXRwdXRGb2xkZXIgPSAiRGF0YS8wMl9Xb3JraW5nLyINCm91dHB1dEltZ0ZvbGRlciA9ICJJbWFnZXMvIg0KckZpbGVOdW0gPSAiMDciDQpyRmlsZU1vZGVsTnVtID0gIjA2Ig0KDQpvdXRwdXRQbmdGb2xkZXI8LWZpbGUucGF0aChvdXRwdXRJbWdGb2xkZXIsIHBhc3RlMChyRmlsZU51bSwiX3BxX3JldmlldyIpKQ0KaWYgKCFkaXIuZXhpc3RzKG91dHB1dFBuZ0ZvbGRlcikpIGRpci5jcmVhdGUob3V0cHV0UG5nRm9sZGVyKQ0KDQpvdmVyd3JpdGUgPSBGQUxTRQ0KYGBgDQoNCg0KYGBge3J9DQojbG9hZCBkYXRhDQojIDAxX3BxX21ldGFjbGVhbi5jc3YNCnBxX21ldGFjbGVhbiA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnRGF0YS8wMl9Xb3JraW5nLzAxX3BxX21ldGFjbGVhbi5jc3YnKQ0KIyByZWFkIGluIGNvbHVtbnMgYXMgY2hhcmFjdGVycyBzbyB0aGF0IGRvYyBpZCBkb2VzIG5vdCByZWFkIGluIGFzIG51bWVyaWMNCiMgMDRfcHFfbGFiZWxzLmNzdg0KcHFfbGFiZWxzIDwtIGRhdGEudGFibGU6OmZyZWFkKHBhc3RlMCgnRGF0YS8wMl9Xb3JraW5nLycsckZpbGVNb2RlbE51bSwnX3BxX2xhYmVscy5jc3YnKSwgY29sQ2xhc3NlcyA9ICdjaGFyYWN0ZXInKQ0KDQojIEhlYWQgZGlzcGxheXMgdGhlIGZpcnN0IDYgcm93cyBvZiB0aGUgZGF0YS50YWJsZQ0KI2hlYWQocHFfbWV0YWNsZWFuKQ0KI2hlYWQocHFfbGFiZWxzKQ0KYGBgDQoNCg0KYGBge3J9DQojIGRpc3BsYXlzIGNvbHVtbiBuYW1lcw0KcHJpbnQoInBxX21ldGFjbGVhbiBjb2x1bW5zOiIpDQpuYW1lcyhwcV9tZXRhY2xlYW4pDQpwcmludCgiIikNCnByaW50KCJwcV9sYWJlbHMgY29sdW1uczoiKQ0KbmFtZXMocHFfbGFiZWxzKQ0KbnJvdyhwcV9tZXRhY2xlYW4pO25yb3cocHFfbGFiZWxzKQ0KYGBgDQoNCiMgam9pbiBjbGVhbmVkIGRhdGFzZXQgd2l0aCBsYWJlbHMNCmBgYHtyfQ0KIyBqb2luIHRhYmxlcw0KIyBpbm5lcl9qb2luIGJlY2F1c2UgcHFfbWV0YWNsZWFuIHdhcyBzdWJzZXQgYmFzZWQgb24gdG9waWMgMiB3aGVuIHRoZSBtb2RlbCB3YXMgcmUtcmFuIGluICIwNF9wcV9tb2RlbC5SbWQiDQojIHNvIHdlIG9ubHkgd2FudCB3aGVyZSB0aGUgUHJvcXVlc3QgSUQgZXhpc3RzIGluIGJvdGggdGhlIG9yaWdpbmFsIGRhdGFzZXQgYW5kIHRoZSBsYWJlbHMuDQpwcV9tZXRham9pbiA8LSBwcV9sYWJlbHMgJT4lIA0KICBpbm5lcl9qb2luKHBxX21ldGFjbGVhbiwgYnkgPSBjKCJkb2N1bWVudCIgPSAiUHJvUXVlc3QgZG9jdW1lbnQgSUQiKSkNCg0KcHFfbWV0YWpvaW4gPC0gcHFfbWV0YWpvaW4gJT4lDQogIHJlbmFtZShgUHJvUXVlc3QgZG9jdW1lbnQgSURgID0gZG9jdW1lbnQpDQoNCm5yb3cocHFfbWV0YWpvaW4pO2hlYWQocHFfbWV0YWpvaW4pDQoNCnBxX2VtcHR5PC1wcV9tZXRham9pbltwcV9tZXRham9pbiR0b3BpYyA9PSAiIiB8IGlzLm5hKHBxX21ldGFqb2luJHRvcGljKSxdDQpucm93KHBxX2VtcHR5KQ0KDQojIHdyaXRlIGxhYmVsZWQgY29ycHVzIHRvIENTVg0Kb3V0cHV0RmlsZU5hbWUgPSAicHFfdG9waWNzIg0Kb3V0cHV0RmlsZSA9IHBhc3RlKG91dHB1dEZvbGRlcixyRmlsZU51bSwiXyIsb3V0cHV0RmlsZU5hbWUsIi54bHN4IixzZXA9IiIpDQppZiAoIWZpbGUuZXhpc3RzKG91dHB1dEZpbGUpIHwgb3ZlcndyaXRlKSB7DQogIHdyaXRlLnhsc3gocHFfbWV0YWpvaW4sIG91dHB1dEZpbGUsIHJvdy5uYW1lcz1GQUxTRSkNCiAgI3dyaXRlLmh0bWx0YWJsZShwcV9tZXRham9pbix0aXRsZT1vdXRwdXRGaWxlLCBvdXRwdXRGaWxlLCBzb3J0Ynk9InRvcGljIiwidmFsIikNCn0NCmBgYA0KIyBDb2RlIG5vdyByZWFkeSBmb3IgU1VCU0VUVElORyBpZiB5b3Ugd2FudCB0byBza2lwIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgLS0NCg0KDQojIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgb2YgdG9waWNzIGdlbmVyYXRlZA0KRG8gd2Ugd2FudCB0byBrZWVwIGJvdGggdG9waWNzLCBvciBkcm9wIG9uZSBvZiB0aGUgdG9waWNzIGFuZCBkaWcgZGVlcGVyIGludG8gdGhlIG90aGVyIHRvcGljPw0KDQojIG51bWJlciBvZiB0b3BpY3MgYnkgcHVibGljYXRpb24gdGl0bGUNCmBgYHtyLCBtZXNzYWdlcz1GQUxTRX0NCnBsb3RUaXRsZSA9ICJDb3VudCBvZiBBcnRpY2xlcyBieSBUb3BpYyBhbmQgUHVibGljYXRpb24gVGl0bGUiDQoNCiMgY291bnQgYnkgcHViIHRpdGxlIGFuZCB0b3BpYw0KY291bnRfdG9waWNfcHVidGl0bGUgPC1wcV9tZXRham9pbiAlPiUNCiAgZ3JvdXBfYnkoYFB1YmxpY2F0aW9uIHRpdGxlYCx0b3BpYykgJT4lIA0KICBzdW1tYXJpc2Uobj0gbigpKSAlPiUNCiAgYXJyYW5nZShhcy5udW1lcmljKHRvcGljKSxhcy5udW1lcmljKG4pKQ0KDQpjb3VudF90b3BpY19wdWJ0aXRsZQ0KDQojIHNhdmUgcGxvdCBpbiBwbmcgZm9ybWF0DQpvdXRwdXRQTkdGaWxlTmFtZSA8LSBmaWxlLnBhdGgob3V0cHV0UG5nRm9sZGVyLHBhc3RlMCgiY291bnRfdG9waWNfcHVidGl0bGUucG5nIikpDQpwbmcob3V0cHV0UE5HRmlsZU5hbWUsaGVpZ2h0PTYsd2lkdGg9MTIsIHVuaXRzPSdpbicsIHJlcz0zMDApDQpnZ3Bsb3QoY291bnRfdG9waWNfcHVidGl0bGUsIGFlcyh4ID0gYXMuZmFjdG9yKGFzLm51bWVyaWModG9waWMpKSwgeSA9IG4sIGZpbGwgPSBgUHVibGljYXRpb24gdGl0bGVgLCBsYWJlbCA9IG4gKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBnZW9tX3RleHQoc2l6ZSA9IDMsIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSArDQogIGdndGl0bGUocGxvdFRpdGxlKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogIHhsYWIoIlRvcGljIikgKw0KICB5bGFiKCJBcnRpY2xlIENvdW50IikgKw0KICBsYWJzKGZpbGwgPSAiVG9waWMiKQ0KcHJpbnQocGFzdGUoIlBsb3Qgc2F2ZWQgYXM6IixvdXRwdXRQTkdGaWxlTmFtZSkpDQpkZXYub2ZmKCkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU51bSwiX3BxX3Jldmlldy9jb3VudF90b3BpY19wdWJ0aXRsZS5wbmciKSkNCmBgYA0KDQojIG51bWJlciBvZiB0b3BpY3MgYnkgbG9jYXRpb24NCg0KIyBDb3VudCBhcnRpY2xlcyBieSB0b3BpYyBhbmQgeWVhcg0KSW1wb3J0YW50IHRvIGNvbnNpZGVyIHRoZSB0b3RhbCBhcnRpY2xlcy95ZWFyIGluIGFkZGl0aW9uIHRvIHRoZSByYXcgY291bnQgb2YgYXJ0aWNsZXMgcGVyIHRvcGljLg0KUGVha3MgaW4gYXJ0aWNsZXMgbWF5IHNpbXBseSBiZSBkdWUgdG8gYW4gb3ZlcmFsbCBpbmNyZWFzZSBvZiBhcnRpY2xlcyBmb3IgYSBwYXJ0aWN1bGFyIHllYXIuDQpgYGB7ciwgbWVzc2FnZXM9RkFMU0V9DQojIGFydGljbGUgY291bnQgcGVyIHllYXIgYW5kIHRvcGljDQpjb3VudF90b3BpY195ZWFyIDwtcHFfbWV0YWpvaW4gJT4lDQogIGdyb3VwX2J5KGBQdWJsaWNhdGlvbiB5ZWFyYCx0b3BpYykgJT4lIA0KICBzdW1tYXJpc2Uobj0gbigpKSAlPiUNCiAgYXJyYW5nZShhcy5udW1lcmljKHRvcGljKSxhcy5udW1lcmljKGBQdWJsaWNhdGlvbiB5ZWFyYCkpDQoNCmNvdW50X3RvcGljX3llYXINCg0KIyBhcnRpY2xlIGNvdW50IHBlciB5ZWFyDQpjb3VudF95ZWFyIDwtcHFfbWV0YWpvaW4gJT4lDQogIGdyb3VwX2J5KGBQdWJsaWNhdGlvbiB5ZWFyYCkgJT4lIA0KICBzdW1tYXJpc2UocGVyWWVhcj0gbigpKSAlPiUNCiAgYXJyYW5nZShhcy5udW1lcmljKGBQdWJsaWNhdGlvbiB5ZWFyYCkpDQoNCmNvdW50X3RvcGljX3llYXI8LWNvdW50X3RvcGljX3llYXIgJT4lDQogIGxlZnRfam9pbihjb3VudF95ZWFyLCBieSA9ICJQdWJsaWNhdGlvbiB5ZWFyIikNCg0KY291bnRfdG9waWNfeWVhcjwtY291bnRfdG9waWNfeWVhciAlPiUNCiAgbXV0YXRlKHJhdGlvID0gcm91bmQobi9wZXJZZWFyLDIpKQ0KDQpwbG90VGl0bGUgPSAiQ291bnQgb2YgQXJ0aWNsZXMgYnkgVG9waWMgYW5kIFllYXIiDQojIHNhdmUgcGxvdCBpbiBwbmcgZm9ybWF0DQpvdXRwdXRQTkdGaWxlTmFtZSA8LSBmaWxlLnBhdGgob3V0cHV0UG5nRm9sZGVyLHBhc3RlMCgiY291bnRfdG9waWNfeWVhci5wbmciKSkNCnBuZyhvdXRwdXRQTkdGaWxlTmFtZSxoZWlnaHQ9NSx3aWR0aD0xNSwgdW5pdHM9J2luJywgcmVzPTMwMCkNCmdncGxvdChjb3VudF90b3BpY195ZWFyLCBhZXMoeCA9IGBQdWJsaWNhdGlvbiB5ZWFyYCwgY29sb3VyID0gYXMuZmFjdG9yKGFzLm51bWVyaWModG9waWMpKSkpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gbikpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEobWluKG5hLm9taXQoY291bnRfdG9waWNfeWVhciRgUHVibGljYXRpb24geWVhcmApKSwgbWF4KG5hLm9taXQoY291bnRfdG9waWNfeWVhciRgUHVibGljYXRpb24geWVhcmApKSwxKSkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGFzLm51bWVyaWModG9waWMpKSkgKw0KICBnZ3RpdGxlKHBsb3RUaXRsZSkgKw0KICB4bGFiKCJQdWJsaWNhdGlvbiBZZWFyIikgKw0KICB5bGFiKCJBcnRpY2xlIENvdW50IikgKw0KICBndWlkZXMoY29sb3VyPUZBTFNFKSANCnByaW50KHBhc3RlKCJQbG90IHNhdmVkIGFzOiIsb3V0cHV0UE5HRmlsZU5hbWUpKQ0KZGV2Lm9mZigpDQoNCnBsb3RUaXRsZSA9ICJDb3VudCBvZiBBcnRpY2xlcyBieSBUb3BpYyBhbmQgWWVhciB3aXRoIFllYXIgVG90YWwiDQojIHNhdmUgcGxvdCBpbiBwbmcgZm9ybWF0DQpvdXRwdXRQTkdGaWxlTmFtZSA8LSBmaWxlLnBhdGgob3V0cHV0UG5nRm9sZGVyLHBhc3RlMCgiY291bnRfdG9waWNfeWVhcl90b3QucG5nIikpDQpwbmcob3V0cHV0UE5HRmlsZU5hbWUsaGVpZ2h0PTUsd2lkdGg9MTUsIHVuaXRzPSdpbicsIHJlcz0zMDApDQpnZ3Bsb3QoY291bnRfdG9waWNfeWVhciwgYWVzKHggPSBgUHVibGljYXRpb24geWVhcmAsIGNvbG91ciA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHRvcGljKSkpKSArDQogIGdlb21fbGluZShhZXMoeSA9IG4pKSArDQogIGdlb21fbGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvcj0iYmxhY2siLCBhZXMoeSA9IHBlclllYXIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKG1pbihuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksIG1heChuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksMSkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogIGZhY2V0X3dyYXAodmFycyhhcy5udW1lcmljKHRvcGljKSkpICsNCiAgZ2d0aXRsZShwbG90VGl0bGUpICsNCiAgeGxhYigiUHVibGljYXRpb24gWWVhciIpICsNCiAgeWxhYigiQXJ0aWNsZSBDb3VudCIpICsNCiAgZ3VpZGVzKGNvbG91cj1GQUxTRSkgDQpwcmludChwYXN0ZSgiUGxvdCBzYXZlZCBhczoiLG91dHB1dFBOR0ZpbGVOYW1lKSkNCmRldi5vZmYoKQ0KDQpwbG90VGl0bGUgPSAiUmF0aW8gb2YgQXJ0aWNsZXMgYnkgVG9waWMgYW5kIFllYXIiDQojIHNhdmUgcGxvdCBpbiBwbmcgZm9ybWF0DQpvdXRwdXRQTkdGaWxlTmFtZSA8LSBmaWxlLnBhdGgob3V0cHV0UG5nRm9sZGVyLHBhc3RlMCgicmF0aW9fdG9waWNfeWVhci5wbmciKSkNCnBuZyhvdXRwdXRQTkdGaWxlTmFtZSxoZWlnaHQ9NSx3aWR0aD0xNSwgdW5pdHM9J2luJywgcmVzPTMwMCkNCmdncGxvdChjb3VudF90b3BpY195ZWFyLCBhZXMoeCA9IGBQdWJsaWNhdGlvbiB5ZWFyYCwgY29sb3VyID0gYXMuZmFjdG9yKGFzLm51bWVyaWModG9waWMpKSkpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gcmF0aW8pKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKG1pbihuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksIG1heChuYS5vbWl0KGNvdW50X3RvcGljX3llYXIkYFB1YmxpY2F0aW9uIHllYXJgKSksMSkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogIGZhY2V0X3dyYXAodmFycyhhcy5udW1lcmljKHRvcGljKSkpICsNCiAgZ2d0aXRsZShwbG90VGl0bGUpICsNCiAgeGxhYigiUHVibGljYXRpb24gWWVhciIpICsNCiAgeWxhYigiKEFydGljbGUgQ291bnQpLyhBcnRpY2xlIFRvdGFsKSIpICsNCiAgZ3VpZGVzKGNvbG91cj1GQUxTRSkgDQpwcmludChwYXN0ZSgiUGxvdCBzYXZlZCBhczoiLG91dHB1dFBOR0ZpbGVOYW1lKSkNCmRldi5vZmYoKQ0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTnVtLCJfcHFfcmV2aWV3L2NvdW50X3RvcGljX3llYXIucG5nIikpDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTnVtLCJfcHFfcmV2aWV3L2NvdW50X3RvcGljX3llYXJfdG90LnBuZyIpKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU51bSwiX3BxX3Jldmlldy9yYXRpb190b3BpY195ZWFyLnBuZyIpKQ0KYGBgDQoNCiMgQ29oZXJlbmNlIHNjb3JlIGJ5IHRvcGljDQpgYGB7ciwgb3V0LndpZHRoPSI1MCUiLCBmaWcucG9zPSJoIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVNb2RlbE51bSwiX3BxX21vZGVsL2NvaGVyZW5jZV9zY29yZV90b3BpYy5wbmciKSkNCmBgYA0KDQojIFdvcmRjbG91ZHMNCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvMV9sZGFfdG9waWNfd2MucG5nIikpDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC8yX2xkYV90b3BpY193Yy5wbmciKSkNCmBgYA0KIyBDb3VudCBvZiBBcnRpY2xlIGJ5IFRvcGljDQpgYGB7ciwgb3V0LndpZHRoPSI1MCUiLCBmaWcucG9zPSJoIn0NCiMgYmFyIGNoYXJ0DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC90b3BpY19jb3VudF9iYXIucG5nIikpDQoNCiMgcGllIGNoYXJ0DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAoIkltYWdlcy8iLHJGaWxlTW9kZWxOdW0sIl9wcV9tb2RlbC90b3BpY19jb3VudF9waWUucG5nIikpDQpgYGANCg0KIyBDb3VudCBvZiBQcm9iYWJpbGl0aWVzIGJ5IFBlcmNlbnRhZ2UNCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvcHJvYl9jb3VudC5wbmciKSkNCmBgYA0KIyBDb3VudCBvZiBUb3BpYyBQcm9iYWJpbGl0aWVzIGJ5IFBlcmNlbnRhZ2UNCmBgYHtyLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5wb3M9ImgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvcHJvYl90b3BpY19jb3VudC5wbmciKSkNCmBgYA0KIzUuIFZpc3VhbGlzaW5nIG9mIHRvcGljcyBpbiBhIGRlbmRyb2dyYW0gLSBub3QgZW5vdWdoIHRvcGljcyAoPDIpDQpgYGB7ciwgb3V0LndpZHRoPSI1MCUiLCBmaWcucG9zPSJoIn0NCiMga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU1vZGVsTnVtLCJfcHFfbW9kZWwvaGNsdXN0X2RlbmRyb2dyYW0ucG5nIikpDQoNCmBgYA0KDQojIFNUQVJUIFNVQlNFVFRJTkcgLSBTdWJzZXQgRnVuY3Rpb24NCiMjIyMgdG9waWNfc3Vic2V0X2NzdiA8LSBmdW5jdGlvbihvdXRwdXRGb2xkZXJOYW1lLCByRmlsZU51bSwgb3V0cHV0RmlsZU5hbWUsIGlucHV0Q29ycHVzLCBtaW5QZXJjLCBtYXhQZXJjLCB0aGVUb3BpYywgb3ZlcndyaXRlKQ0KDQoqIG91dHB1dEZvbGRlck5hbWUgPSBmb2xkZXIgdG8gc2F2ZSB0byAtLSBpLmUuLCAiRGF0YS8wMl9Xb3JraW5nLyIgKHRoaXMgaXMgc2V0IGF0IHRoZSB0b3Agb2YgdGhlIGNvZGUgd2hlcmUgdGhlIGxpYnJhcmllcyBhcmUgbG9hZGVkL2luc3RhbGxlZCkNCiogckZpbGVOdW0gPSBUaGUgbnVtYmVyIGluIHRoZSByIGZpbGUgLS0gaS5lLiwgIjA1IiAodGhpcyBpcyBzZXQgYXQgdGhlIHRvcCBvZiB0aGUgY29kZSB3aGVyZSB0aGUgbGlicmFyaWVzIGFyZSBsb2FkZWQvaW5zdGFsbGVkKQ0KKiBvdXRwdXRGaWxlTmFtZSA9IHRoZSBmaWxlIG5hbWUgdG8gc2F2ZSBhcyAtLSBpLmUuLCAicHFfdG9waWM1XzkwcGVyYyIgKHRoaXMgbmVlZHMgdG8gYmUgc2V0IGF0IGxlYXN0IDEgbGluZSBhYm92ZSB3aGVyZSB0aGUgZnVuY3Rpb24gaXMgcmFuKQ0KKiBpbnB1dENvcnB1cyA9IHRoZSBsYWJlbGVkIGNvcnB1cyAtLSBpLmUuLCBwcV9tZXRham9pbiAodGhpcyBpcyBjcmVhdGVkIHNob3J0bHkgYWZ0ZXIgd2hlcmUgbGlicmFyaWVzIGFyZSBsb2FkZWQvaW5zdGFsbGVkIGluIHJldmlldy5SbWQgYnkgam9pbmluZyBwcV9tZXRhY2xlYW4gd2l0aCBwcV9sYWJlbHMpDQoqIG1pblBlcmMgPSBtaW5pbXVtIHBlcmNlbnRhZ2UgLS0gaS5lLiwgMC45ICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogbWF4UGVyYyA9IG1heGltdW0gcGVyY2VudGFnZSAtLSBpLmUuLCAxICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogdGhlVG9waWMgPSB0aGUgdG9waWMgY2F0ZWdvcnkgLS0gaS5lLiwgIjEiICh0aGlzIG5lZWRzIHRvIGJlIHNldCBhdCBsZWFzdCAxIGxpbmUgYWJvdmUgd2hlcmUgdGhlIGZ1bmN0aW9uIGlzIHJhbikNCiogb3ZlcndyaXRlID0gd2hldGhlciB0byBvdmVyd3JpdGUgdGhlIGZpbGUgaWYgaXQgYWxyZWFkeSBleGlzdHMgLS0gaS5lLiwgRkFMU0UgKHRoaXMgbmVlZHMgdG8gYmUgc2V0IGF0IGxlYXN0IDEgbGluZSBhYm92ZSB3aGVyZSB0aGUgZnVuY3Rpb24gaXMgcmFuKQ0KDQojIFN1YnNldCB0b3BpYyAxDQpgYGB7cn0NCiMgc3Vic2V0IGNvcnB1cyB0byB1bmlxdWUgaWRlbnRpZmllciAmIGZ1bGwgdGV4dCBvZiBhcnRpY2xlDQojIGludmVzdGlnYXRlIHRvcGljIDENCg0Kb3V0cHV0RmlsZU5hbWVfMXQgPC0gInBxX3RvcGljMSINCm1pblBlcmMgPC0gMA0KbWF4UGVyYyA8LSAxDQp0aGVUb3BpYyA8LSAiMSINCg0KIyMgZS5nLCBtaW5QZXJjID0gMCAmIG1heFBlcmMgPSAwLjQgaXMgemVybyB0byAwLjM5OTk5OTk5OTkgLi4uDQpwcV90b3BpYzFfc3Vic2V0PC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVOYW1lXzF0LCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfdG9waWMxX3N1YnNldCk7bnJvdyhwcV90b3BpYzFfc3Vic2V0KQ0KYGBgDQojIFN1YnNldCB0b3BpYyAyDQpgYGB7cn0NCiMgc3Vic2V0IGNvcnB1cyB0byB1bmlxdWUgaWRlbnRpZmllciAmIGZ1bGwgdGV4dCBvZiBhcnRpY2xlDQojIGludmVzdGlnYXRlIHRvcGljIDINCg0Kb3V0cHV0RmlsZU5hbWVfMXQgPC0gInBxX3RvcGljMiINCm1pblBlcmMgPC0gMA0KbWF4UGVyYyA8LSAxDQp0aGVUb3BpYyA8LSAiMiINCg0KIyMgZS5nLCBtaW5QZXJjID0gMCAmIG1heFBlcmMgPSAwLjQgaXMgemVybyB0byAwLjM5OTk5OTk5OTkgLi4uDQpwcV90b3BpYzFfc3Vic2V0PC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVOYW1lXzF0LCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfdG9waWMxX3N1YnNldCk7bnJvdyhwcV90b3BpYzFfc3Vic2V0KQ0KYGBgDQoNCiMgU3Vic2V0IHRvcGljIDEsIDkwJQ0KYGBge3J9DQojIHN1YnNldCBjb3JwdXMgdG8gdW5pcXVlIGlkZW50aWZpZXIgJiBmdWxsIHRleHQgb2YgYXJ0aWNsZQ0KIyBpbnZlc3RpZ2F0ZSB0b3BpYyAxLCA5MCUNCm91dHB1dEZpbGVfMXQ5MHBlcmMgPC0gInBxX3RvcGljMV85MHBlcmMiDQptaW5QZXJjIDwtIDAuOQ0KbWF4UGVyYyA8LSAxDQp0aGVUb3BpYyA8LSAiMSINCg0KIyMgZS5nLCBtaW5QZXJjID0gMCAmIG1heFBlcmMgPSAwLjQgaXMgemVybyB0byAwLjM5OTk5OTk5OTkgLi4uDQpwcV90b3BpYzFfOTBwZXJjPC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVfMXQ5MHBlcmMsIHBxX21ldGFqb2luICwgbWluUGVyYywgbWF4UGVyYywgdGhlVG9waWMsIG92ZXJ3cml0ZSkNCg0KaGVhZChwcV90b3BpYzFfOTBwZXJjKTtucm93KHBxX3RvcGljMV85MHBlcmMpDQpgYGANCg0KDQojIFN1YnNldCB0b3BpYyAyLCA5MCUNCmBgYHtyfQ0KIyBzdWJzZXQgY29ycHVzIHRvIHVuaXF1ZSBpZGVudGlmaWVyICYgZnVsbCB0ZXh0IG9mIGFydGljbGUNCiMgaW52ZXN0aWdhdGUgdG9waWMgMiwgOTAlDQpvdXRwdXRGaWxlXzV0OTBwZXJjIDwtICJwcV90b3BpYzJfOTBwZXJjIg0KbWluUGVyYyA8LSAwLjkNCm1heFBlcmMgPC0gMQ0KdGhlVG9waWMgPC0gIjIiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC40IGlzIHplcm8gdG8gMC4zOTk5OTk5OTk5IC4uLg0KcHFfdG9waWM1XzkwcGVyYzwtdG9waWNfc3Vic2V0X2NzdihvdXRwdXRGb2xkZXIsIHJGaWxlTnVtLCBvdXRwdXRGaWxlXzV0OTBwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfdG9waWM1XzkwcGVyYyk7bnJvdyhwcV90b3BpYzVfOTBwZXJjKQ0KYGBgDQoNCg0KIyBTdWJzZXQgNTAtNjAlLWVycw0KYGBge3J9DQojIHN1YnNldCBjb3JwdXMgdG8gdW5pcXVlIGlkZW50aWZpZXIgJiBmdWxsIHRleHQgb2YgYXJ0aWNsZQ0KIyBpbnZlc3RpZ2F0ZSBhbGwgdG9waWNzLCA1MC02MCUNCm91dHB1dEZpbGVfNTZwZXJjIDwtICJwcV81NnBlcmMiDQptaW5QZXJjIDwtIDAuNQ0KbWF4UGVyYyA8LSAwLjYNCnRoZVRvcGljIDwtICJhbGwiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC40IGlzIHplcm8gdG8gMC4zOTk5OTk5OTk5IC4uLg0KcHFfNTZwZXJjPC10b3BpY19zdWJzZXRfY3N2KG91dHB1dEZvbGRlciwgckZpbGVOdW0sIG91dHB1dEZpbGVfNTZwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfNTZwZXJjKTtucm93KHBxXzU2cGVyYykNCmBgYA0KDQojIFN1YnNldCAwLTYwJS1lcnMNCmBgYHtyfQ0KIyBzdWJzZXQgY29ycHVzIHRvIHVuaXF1ZSBpZGVudGlmaWVyICYgZnVsbCB0ZXh0IG9mIGFydGljbGUNCiMgaW52ZXN0aWdhdGUgYWxsIHRvcGljcywgMC02MCUNCiMgaWYgd2l0aGluIHRoZSBmdW5jdGlvbiB5b3Ugd3JpdGUgb3ZlcndyaXRlPVRSVUUsIHRoZW4gdGhlIG91dHB1dCBDU1YgZmlsZSB3aWxsIGJlIHJlLXdyaXR0ZW4uIA0KIyBvdmVyd3JpdGUsIG91dHB1dEZvbGRlciwgckZpbGVOYW1lIGFyZSBzZXQgaW4gdGhlIGZpcnN0IGNodW5rIG9mIGNvZGUNCg0KDQpvdXRwdXRGaWxlTmFtZV8wNnBlcmMgPC0gInBxXzA2cGVyYyINCm1pblBlcmMgPC0gMA0KbWF4UGVyYyA8LSAwLjYNCnRoZVRvcGljIDwtICJhbGwiDQoNCiMjIGUuZywgbWluUGVyYyA9IDAgJiBtYXhQZXJjID0gMC42IGlzIHplcm8gdG8gMC41OTk5OTk5OSAuLi4NCnBxX3BlcmMwNl9zdWJzZXQ8LXRvcGljX3N1YnNldF9jc3Yob3V0cHV0Rm9sZGVyLCByRmlsZU51bSwgb3V0cHV0RmlsZU5hbWVfMDZwZXJjLCBwcV9tZXRham9pbiAsIG1pblBlcmMsIG1heFBlcmMsIHRoZVRvcGljLCBvdmVyd3JpdGUpDQoNCmhlYWQocHFfcGVyYzA2X3N1YnNldCk7bnJvdyhwcV90b3BpYzFfc3Vic2V0KQ0KdW5pcXVlKHBxX3BlcmMwNl9zdWJzZXQkdG9waWMpDQptaW4ocHFfcGVyYzA2X3N1YnNldCR2YWwpDQptYXgocHFfcGVyYzA2X3N1YnNldCR2YWwpDQpgYGANCg0KIyBFTkQgU1VCU0VUVElORw0KDQojIGV4cGxvcmUgdGVybSBmcmVxdWVuY2llcyBieSB5ZWFyIC0gaG93IGRvIHdvcmRzIGluIG91ciBjb3JwdXMgY2hhbmdlIG92ZXIgdGltZT8NClJlZmVyZW5jZWQgd2Fsay10aHJvdWdoIGZyb206IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5dGV4dC92aWduZXR0ZXMvdGlkeWluZ19jYXN0aW5nLmh0bWwNCkFyZSBhcnRpY2xlcyBub3JtYWxpemVkIGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgYXJ0aWNsZXMgcGVyIHllYXINCmBgYHtyfQ0KIyBzdWJzZXQgY29ycHVzIHRvIHVuaXF1ZSBpZGVudGlmaWVyICYgeWVhcg0KcHFfdGltZSA8LSBwcV9tZXRhY2xlYW4gJT4lIA0KICBzZWxlY3QoYFByb1F1ZXN0IGRvY3VtZW50IElEYCwgYFB1YmxpY2F0aW9uIHllYXJgKQ0KDQojIHRva2VucyBnZW5lcmF0ZWQgZnJvbSAwNl9wcV9tb2RlbC5SbWQNCm91dHB1dFRva2VuRmlsZSA9IHBhc3RlMChyRmlsZU1vZGVsTnVtLCJfdG9rZW5zLlJEYXRhIikNCnRva2Vuc0ZpbGVOYW1lID0gZmlsZS5wYXRoKG91dHB1dEZvbGRlcixvdXRwdXRUb2tlbkZpbGUpDQoNCmxvYWQoZmlsZT10b2tlbnNGaWxlTmFtZSkNCg0KdG9rZW5zIDwtIHRva2VucyAlPiUgDQogIGZ1bGxfam9pbihwcV90aW1lLCBieSA9IGMoIlByb1F1ZXN0IGRvY3VtZW50IElEIiA9ICJQcm9RdWVzdCBkb2N1bWVudCBJRCIpKSAlPiUNCiAgcmVuYW1lKFllYXIgPSBgUHVibGljYXRpb24geWVhcmApICU+JQ0KICByZW5hbWUocHFfaWQgPSBgUHJvUXVlc3QgZG9jdW1lbnQgSURgKSAlPiUNCiAgbXV0YXRlX2F0KHZhcnMoWWVhciksIGZ1bnMoYXMuaW50ZWdlcikpDQpybShwcV90aW1lKQ0KDQpvdXRwdXRGcmVxc0ZpbGUgPSBwYXN0ZTAockZpbGVOdW0sIl90b2tlbnNfZnJlcSIpDQp0b2tlbnNfZnJlcTwtY3JlYXRlX2lmbm90X3Rva2Vuc19mcmVxKG91dHB1dEZvbGRlciwgb3V0cHV0RnJlcXNGaWxlLCB0b2tlbnMsIG92ZXJ3cml0ZSkNCg0Kb3V0cHV0RnJlcU1vZGVsRmlsZSA9IHBhc3RlMChyRmlsZU51bSwiX2ZyZXFfbW9kZWxzIikNCmZyZXFfbW9kZWxzIDwtIGNyZWF0ZV9pZm5vdF9mcmVxbW9kZWxzKG91dHB1dEZvbGRlciwgb3V0cHV0RnJlcU1vZGVsRmlsZSwgdG9rZW5zX2ZyZXEsIG92ZXJ3cml0ZSkNCmBgYA0KDQojIG1vZGVsIHJlc3VsdHMNCmBgYHtyfQ0KZnJlcV9tb2RlbHMgJT4lDQogIGZpbHRlcih0ZXJtID09ICJZZWFyIikgJT4lDQogIGFycmFuZ2UoZGVzYyhhYnMoZXN0aW1hdGUpKSkNCmBgYA0KDQojIE1vZGVscyBkaXNwbGF5ZWQgYXMgYSB2b2xjYW5vIHBsb3QsIHdoaWNoIGNvbXBhcmVzIHRoZSBlZmZlY3Qgc2l6ZSB3aXRoIHRoZSBzaWduaWZpY2FuY2UNCmBgYHtyfQ0KIyBzYXZlIHBsb3QgaW4gcG5nIGZvcm1hdA0Kb3V0cHV0UE5HRmlsZU5hbWUgPC0gZmlsZS5wYXRoKG91dHB1dFBuZ0ZvbGRlcixwYXN0ZTAoIndvcmRfY2hhbmdlX292ZXJfdGltZS5wbmciKSkNCnBuZyhvdXRwdXRQTkdGaWxlTmFtZSxoZWlnaHQ9NSx3aWR0aD0xNSwgdW5pdHM9J2luJywgcmVzPTMwMCkNCg0KZnJlcV9tb2RlbHMgJT4lDQogIG11dGF0ZShhZGp1c3RlZC5wLnZhbHVlID0gcC5hZGp1c3QocC52YWx1ZSkpICU+JQ0KICBnZ3Bsb3QoYWVzKGVzdGltYXRlLCBhZGp1c3RlZC5wLnZhbHVlKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBzY2FsZV95X2xvZzEwKCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gd29yZCksIHZqdXN0ID0gMSwgaGp1c3QgPSAxLA0KICAgICAgICAgICAgY2hlY2tfb3ZlcmxhcCA9IFRSVUUpICsNCiAgeGxhYigiRXN0aW1hdGVkIGNoYW5nZSBvdmVyIHRpbWUiKSArDQogIHlsYWIoIkFkanVzdGVkIHAtdmFsdWUiKQ0KDQoNCnByaW50KHBhc3RlKCJQbG90IHNhdmVkIGFzOiIsb3V0cHV0UE5HRmlsZU5hbWUpKQ0KZGV2Lm9mZigpDQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhc3RlMCgiSW1hZ2VzLyIsckZpbGVOdW0sIl9wcV9yZXZpZXcvd29yZF9jaGFuZ2Vfb3Zlcl90aW1lLnBuZyIpKQ0KYGBgDQoNCiMgVG9wIDYgdGVybXMgdGhhdCBoYXZlIGNoYW5nZWQgaW4gZnJlcXVlbmN5IG92ZXIgdGltZQ0KYGBge3J9DQojIHNhdmUgcGxvdCBpbiBwbmcgZm9ybWF0DQpvdXRwdXRQTkdGaWxlTmFtZSA8LSBmaWxlLnBhdGgob3V0cHV0UG5nRm9sZGVyLHBhc3RlMCgidG9wX3dvcmRfY2hhbmdlX292ZXJfdGltZS5wbmciKSkNCnBuZyhvdXRwdXRQTkdGaWxlTmFtZSxoZWlnaHQ9NSx3aWR0aD0xNSwgdW5pdHM9J2luJywgcmVzPTMwMCkNCg0KZnJlcV9tb2RlbHMgJT4lDQogIHRvcF9uKDYsIGFicyhlc3RpbWF0ZSkpICU+JQ0KICBpbm5lcl9qb2luKHRva2Vuc19mcmVxKSAlPiUNCiAgZ2dwbG90KGFlcyhZZWFyLCBwZXJjZW50KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh+IHdvcmQpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnRfZm9ybWF0KCkpICsNCiAgeWxhYigiRnJlcXVlbmN5IG9mIHdvcmQgaW4gc3BlZWNoIikNCg0KcHJpbnQocGFzdGUoIlBsb3Qgc2F2ZWQgYXM6IixvdXRwdXRQTkdGaWxlTmFtZSkpDQpkZXYub2ZmKCkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGFzdGUwKCJJbWFnZXMvIixyRmlsZU51bSwiX3BxX3Jldmlldy90b3Bfd29yZF9jaGFuZ2Vfb3Zlcl90aW1lLnBuZyIpKQ0KYGBgDQo=